Rtrofit 和 RxJava 这两年可是大为火热,基本上新的 APP 都会使用这两个东西。如果作为一个 Android 开发者还不知道有这些东西,那你就真的 OUT 了。
简单介绍
Retrofit
说到 Retrofit 就要说 OkHttp 。因为 Retrofit 是对 OkHttp 的封装。
OkHttp
说到 OkHttp , 又要说到 Android 的几种 Http 请求方式。
-
HttpClint : 官方提供的 Http 请求方式,但是在 Android 5.0 之后,官方就废弃了,而且从系统中移除了,这里就不在讨论了。我想,大家一开始都是从这里开始学 Android 的网络请求的吧。
-
HttpUrConnection : 官方提供的 Http 请求方式,但是不好用。(其实,我也不知道哪里不好,但是大家都说不好用。。。)
-
OkHttp :由 square 团队开发,据说在底层的实现也是自成一派。具有高效的请求效率。
OkHttp 虽然好用,但是在日常的使用中,需要更多频繁和繁琐的操作,这个时候就需要封装一下了。Retrofit 这个时候就顺势登场了。
Retrofit 最具有特色的就是,把请求的接口实例成了 Java 中的接口,还有,就是有很多的注解,可以很方便的实现某些功能。
但是,本文不是介绍 Rtrofit ,这里就不多做介绍。
RxJava
一个异步处理的库。很牛逼。
这个库可以把异步的操作,写成链式的代码,这样看起来就清晰了很多。也提供了多种操作符,让我们使用起来很舒服。
下面贴一个链接,可谓是 RxJava 入门的必看之作。
http://gank.io/post/560e15be2dca930e00da1083?winzoom=1
给 Android 开发者的 RxJava 详解
正式开始
本文的大部分封装灵感来自下面这篇文章
http://gank.io/post/56e80c2c677659311bed9841
RxJava 与 Retrofit 结合的最佳实践
一般后台都会返回类似下面的 json
{
"resultCode": 0,
"resultMessage": "成功",
"data": {}
}
然后一般的网络请求是这样的
先用 Retrofit 写一个请求接口
public interface MovieService {
@GET("top250")
Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
//进行网络请求
private void getMovie(){
//豆瓣电影的接口
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
movieService.getTopMovie(0, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MovieEntity>() {
@Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
resultTV.setText(e.getMessage());
}
@Override
public void onNext(MovieEntity movieEntity) {
resultTV.setText(movieEntity.toString());
}
});
}
Wa , 一个网络请求要这么多代码,那怎么行。
首先分解一下步骤:
- 创建 Retrofit (设置一些基本参数,如:baseUrl ,超时时间 等)
- 创建 Service (根据接口生成实际的 接口对象)
- 创建 Observer (接收接口返回的数据,并处理)
- 订阅 (把 Service 和 Observer 关联起来,也就是联通起来)
开始封装
HttpManager
对于同一个 APP 来讲,网络请求的一些基本参数,如:baseUrl ,请求超时的时间的等,基本上是一致的。
而且很多情况下,不需要重复创建 Retrofit 对象,所以只需要一个就行了。
把所有的接口都写在一起,也只需要一个 Service 对象就可以了。
还有就是为了方便调试,我们要加一个拦截器,把接口接收到的数据打印出来。
结合这些需求,我们就用 单例模式 来做
public class HttpManager {
private final static String BASE_URL="http:// 地址 + 端口";
//超时时间
private final static int DEFAULT_TIMEOUT = 60;
private OkHttpClient.Builder builder;
private Retrofit rxRetrofit;
private static HttpManager instance;
private APIService service;
private OkHttpClient recordClient;
public OkHttpClient getRecordClient() {
return recordClient;
}
public Retrofit getRecordRetrofit() {
return recordRetrofit;
}
public Retrofit getRxRetrofit() {
return rxRetrofit;
}
public APIService getService() {
return service;
}
//构造方法私有化
private HttpManager(){
if (builder==null) {
builder = new OkHttpClient.Builder()
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
}
}
if (rxRetrofit==null) {
rxRetrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
if (recordClient==null){
recordClient=builder.build();
}
if (service==null){
service=rxRetrofit.create(APIService.class);
}
}
//获取单例对象
public static HttpManager getInstance(){
if (instance ==null){
instance =new HttpManager();
}
return instance;
}
}
好的,很简单,继续
我们知道,Retrofit 是支持类型转换的,在上面我们已经添加了 Gson 的解析器。
但是,问题又来了,每次请求过来的数据都是不一样的,我们总不能,每一个都去写一个 Bean 类吧。
还好,一般都会有一定的数据格式。比如下面:
{
"resultCode": 0,
"resultMessage": "成功",
"data": {}
}
然后解析的对象,大概可以这么写
public class HttpResult<T> {
private int resultCode;
private String resultMessage;
private T data;
}
但是,我公司的结构大概是这样子的:
{
"code": 0,
"content": "成功",
}
你们可能会问,怎么少了一个。
其实不是少了一个,而是两个融合在一起了。。。。
那就尴尬了。然后去找后台大哥商讨,后台大哥说,我们公司的框架就是这样子的,改不了。。。。。
看来就只能我这边自己搞了。
其实也不难就是繁琐了一点,就多了一个步骤。
一般的处理步骤可以参考一下上面 推荐的文章 。
然后数据模型,就大概是这样子了。
BaseModel
public class BaseModel {
private int code;
private String content;
}
接口写起来就简单多了。
APIService
public interface APIService {
/**
* 登录
*/
@FormUrlEncoded
@POST("/login/checkLogin")
Observable<BaseModel> login(@Field("userNumber") String userNumber);
}
以前的时候,为了方便,我就直接把 参数 和 Observer 都传进来,在内部直接就 Observable 和 observer 关联起来了。
/**
* 登录
*/
public static void login(String userCode,String passwd,Observer<String> observer){
Observable<BaseModel> Observable = getKaoQinAPI().signIn( userCode,name, passwd);
observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
用起来也是很爽。
APIMethods.login("10086","10010",new new Observer<String>(){
@Override
public void onSubscribe(Disposable d) {
//开始
}
@Override
public void onNext(String value) {
//接口返回数据
}
@Override
public void onError(Throwable e) {
//出错
}
@Override
public void onComplete() {
//完成
}
});
真的是一行代码,就可以了。
但是,这样写有一个很明显的问题,有时候要连续两次调用接口或者几个连续的操作。比如,要上传几张,还有一段文本,还有,一些用户信息。然后再上传图片前还要进行压缩。又或者发送请求后接收数据,然后对数据进行处理,然后再进行 IO 操作,最后要显示到界面上。
那么问题来了,按照之前的写法,就很被动了,这个过程就要被拆分开来写,或者进行嵌套。
那就违背了 RxJava 的设计初衷了,既然用了 RxJava 就要用到底。RxJava 就是为了解决这些复杂的操作,把这些操作转化成链式的步骤。那么逻辑就清晰很多了。
那就不能像上面那样写了。而是要把 Observer 返回出去,一旦拿到 Observer 再配合 RxJava 的各种操作符,就可以把整个过程串联起来了。
/**
* 登录
*
* @param userCode 用户账号
*/
public static Observable<BaseModel> login(String userCode) {
return getAPIService()
.login(userCode);
}
但是有一点不爽,就是我们实际要使用的数据其实就只有 content 的这个字段而已,如果我们每次都在要用到的时候都要重新把数据拆分开,那就很麻烦了,这个时候就用到 RxJava 的 map 接口了。
map
这个接口可以把一种 数据类型的 Observable 转化成 另一种数据类型的 Observable ,我们就可以把 BaseModel 转化为 Content(其实就是 String 类型)
PreFunction
/**
* 对数据进行预先处理
*/
public class PreFunction implements Function<BaseModel, String> {
@Override
public String apply(BaseModel baseModel) throws Exception {
return baseModel.getContent();
}
}
但是又没这么简单,因为,如果请求出现问题。比如,可能服务器异常,session 过期 等等的情况,这个时候取出 content 就有问题了。
所以我们要先对返回的数据进行筛选和处理。现在 RxJava 2 是支持在 Function 里面直接抛出异常的,然后会在 onError 里面捕获到。
先定义一种异常,用来标识服务器返回的异常。
ApiException
public class ApiException extends RuntimeException {
//服务器异常 ---对应 code == 4
public static final int SERVER_EXCEPTION = 4;
//令牌过期 或 在其他客户端登录 --- 对应 code ==3
public static final int SESSION_EXCEPTION = 3;
private static String exception;
public int errorCode=-1;
public static String getException() {
return exception;
}
public ApiException(int resultCode) {
this(getExceptionMessage(resultCode));
errorCode=resultCode;
}
public ApiException(int resultCode, String message){
this(message);
errorCode=resultCode;
}
/**
* 由于服务器传递过来的错误信息直接给用户看的话,用户未必能够理解
* 需要根据错误码对错误信息进行一个转换,再显示给用户
*/
public static String getExceptionMessage(int code){
exception = "未知异常";
switch (code){
case SERVER_EXCEPTION:
exception ="服务器异常";
break;
case SESSION_EXCEPTION:
exception ="令牌过期 或 账号异地登录";
break;
}
return exception;
}
public ApiException(String detailMessage) {
super(detailMessage);
exception=detailMessage;
}
}
预处理就可以这么写
/**
* 对数据进行预先处理
*/
public class PreFunction implements Function<BaseModel, String> {
@Override
public String apply(BaseModel baseModel) throws Exception {
if (baseModel.getCode() == 2) {
//code == 2 的时候要输出信息给用户
throw new ApiException(baseModel.getCode(),baseModel.getContent());
} else if (baseModel.getCode() != 1) {
//code != 1 是说明请求出错
throw new ApiException(baseModel.getCode());
}
return baseModel.getContent();
}
}
然后,返回的 Observer 就可以这么写
/**
* 登录
*
* @param userCode 关员号
*/
public static Observable<String> login(String userCode) {
return getAPIService()
.login(userCode)
.map(new PreFunction());
}
这样就可以直接拿到 content 了。
Observable 和 Observer 之间的订阅 ,其实也都是差不多的,也可以提取出来
public static <T> void toSubscribe(Observable<T> observable, Observer<T> observer) {
observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
使用起来是这样的
Observable<String> observable = APIMethods.login(User_Code);
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
//登录成功
}
@Override
public void onError(Throwable e) {
//出错
}
@Override
public void onComplete() {
}
};
APIMethods.toSubscribe(observable,observer);
用起来也可以,但是就是 接口里面的方法 有好几个是我们不需要的,又占位置,又看起来不舒服,改!
我的想法是写一个类,实现接口的所有方法,然后用的时候,需要哪个接口,重写就行了。
HttpResultObserver
public class HttpResultObserver<T> implements Observer<T> {
//用来取消请求
public Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable=d;
}
@Override
public void onNext(T value) {
}
@Override
public void onError(Throwable e) {
//如果是我们自己定义的异常,就转化成描述后,显示给用户看
if (e instanceof ApiException) {
String exception = ((ApiException) e).getException();
Toast.makeText(MyApplication.getINSTANCE(), exception, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onComplete() {
}
}
嗯,到这来就差不多了。
还有一个需求,就是发送网络请求时需要有一个加载框,然后网络请求结束后,就关闭加载框。
在推荐的文章中是使用 Handler 来做。虽然说不上来哪里不好,但是总感觉怪怪的。而且就像前面我以前犯的错误类似,就把加载框和网络请求绑死了。要是要和其他异步操作衔接就比较麻烦了。
我是用接口来做的。
先定义一个接口
DialogDisplay
public interface DialogDisplay {
//显示 dialog
void showDialog(DialogCancelListener dialogCancelListener,boolean canCancelable);
//关掉 dialog
void dismissDialog();
}
然后在 BaseActivity 中实现这个接口
@Override
public void showDialog(final DialogCancelListener dialogCancelListener,boolean canCancelable) {
if(loadingDialog ==null){
loadingDialog = new ProgressDialog(this);
loadingDialog.setMessage("正在加载中。。。");
}
loadingDialog.setCancelable(canCancelable);
if (canCancelable){
loadingDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (dialogCancelListener!=null){
dialogCancelListener.onCancelProgress();
}
}
});
}
if (!loadingDialog.isShowing()){
loadingDialog.show();
}
}
@Override
public void dismissDialog() {
if (loadingDialog !=null){
loadingDialog.dismiss();
loadingDialog =null;
}
}
然后把 BaseActivity 传入 observer ,让 observer 来控制 加载框。
既然要封装就要彻底。
写两个接口
OnSuccessListener
public interface OnSuccessListener<T> {
void onSuccess(T result);
}
OnFailureListener
public interface OnFailureListener {
void onFailure(Throwable throwable);
}
然后写个抽象类,实现这两个接口,但是不实现 OnSuccess 方法,使用的时候就必须实现这个方法,就不用,每次去手动重写。(Android Studio 会自动填充 抽象方法)
还有就是 加载框 可否取消,取消后要进行什么操作。
嗯,也用接口来做
DialogCancelListener
public interface DialogCancelListener {
void onCancelProgress();
}
搞完之后大概就这样子了
HttpProgressDialogObserver
public abstract class HttpProgressDialogObserver<T> extends HttpResultObserver<T>
implements DialogCancelListener,OnSuccessListener<T>,OnFailureListener{
private final DialogDisplay dialogDisplay;
private boolean canCancelable;
public HttpProgressDialogObserver(DialogDisplay dialogDisplay,boolean canCancelable) {
this.dialogDisplay =dialogDisplay;
this.canCancelable=canCancelable;
}
@Override
public void onSubscribe(Disposable d) {
super.onSubscribe(d);
if (dialogDisplay!=null){
dialogDisplay.showDialog(this,canCancelable);
}
}
@Override
public void onNext(T value) {
super.onNext(value);
onSuccess(value);
}
@Override
public void onComplete() {
super.onComplete();
if (dialogDisplay!=null){
dialogDisplay.dismissDialog();
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
if (dialogDisplay!=null){
dialogDisplay.dismissDialog();
}
onFailure(e);
}
@Override
public void onCancelProgress() {
if (disposable!=null){
if (!disposable.isDisposed()){
disposable.dispose();
}
}
}
@Override
public void onFailure(Throwable throwable) {
}
}
用起来大概是这样子的
Observable<String> login = APIMethods.login(User_Code);
HttpProgressDialogObserver<String> loginObserver = new HttpProgressDialogObserver<String>(LoginActivity.this, false) {
@Override
public void onSuccess(String result) {
}
};
APIMethods.toSubscribe(login,loginObserver);
今天在整理之前的笔记的时候,发现这个已经有点旧了,后面在这个基础上改进一些方法,后面又时间在写一篇文章吧。这里就补充一个点。
RxJava 的链式结构呢?
用过 RxJava 的同学应该都知道,RxJava 的链式结构,让人能够对发生的事情的顺序一目了然,很清晰。但是,我们上面的这个结构很明显被切分成了三段,这样就破坏了 RxJava 优雅的链式结构,这能忍?
之前没有找到好的解决方法之前,也就忍了,后面在看到一些项目使用到了一个 ObservableTransformer
的东西,那时候就在想,“卧槽,这不就是我要找的东西嘛!” 。真是相见恨晚,查了一下资料后马上就用上了。
代码大概是这样:
先创建一个 ObservableTransformer
Transformer实际上就是一个Func1<Observable
, Observable >,换言之就是:可以通过它将一种类型的Observable转换成另一种类型的Observable,和调用一系列的内联操作符是一模一样的。
public static ObservableTransformer<BaseModel,String> io_main(){
return new ObservableTransformer<BaseModel, String>() {
@Override
public ObservableSource<String> apply(Observable<BaseModel> upstream) {
return upstream.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new PreFunction());
}
};
}
然后使用 compose
操作符,进行处理。
compose()是唯一一个能够从数据流中得到原始Observable
的操作符,所以,那些需要对整个数据流产生作用的操作(比如,subscribeOn()和observeOn())需要使用compose()来实现。
APIMethods.login(User_Code)
.compose(APIMethods.io_main())
.subscribe(
new HttpProgressDialogObserver<String>(LoginActivity.this, false) {
@Override
public void onSuccess(String result) {
}
});
使用起来就和上面看到的一样,是如此的简洁、清晰。这真的是太棒了!
拜拜
讲完了,拜拜。